package com.gframework.boot.mvc.config;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;

import org.jasig.cas.client.Protocol;
import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.jasig.cas.client.configuration.ConfigurationKeys;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.AssertionHolder;
import org.jasig.cas.client.validation.AbstractTicketValidationFilter;
import org.jasig.cas.client.validation.Assertion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.context.annotation.Bean;
import org.springframework.util.StringUtils;

import com.gframework.annotation.EnableCasClientSecurity;
import com.gframework.autoconfigure.mvc.cas.CasExtendConfigurationProperties;
import com.gframework.boot.mvc.controller.session.cas.CasSessionUserCreateFilter;

import net.unicon.cas.client.configuration.CasClientConfiguration;
import net.unicon.cas.client.configuration.CasClientConfigurerAdapter;

/**
 * 单点登录相关配置.<br>
 * 本类由 {@link EnableCasClientSecurity} 注解进行启动
 * 
 * <p>CAS核心的逻辑处理过滤器是：
 * <li>重定向控制：{@link AuthenticationFilter}</li>
 * <li>ticket验证：{@link AbstractTicketValidationFilter}抽象类的相关子类（根据cas协议号的不同，子类不同）</li>
 * 
 * @since 1.0.0
 * @author Ghwolf
 */
@SuppressWarnings("rawtypes")
@EnableConfigurationProperties(CasExtendConfigurationProperties.class)
@ConditionalOnClass({Assertion.class,AbstractCasFilter.class,AssertionHolder.class})
public class CasConfig extends CasClientConfigurerAdapter {
	private static final Logger logger = LoggerFactory.getLogger(CasConfig.class);

	/**
	 * cas 非标准额外配置信息
	 */
	@Autowired
	private CasExtendConfigurationProperties casConfiguration;
	

	/**
	 * 允许post提交ticket的代理类
	 * @author Ghwolf
	 */
	class RetrieveTicketFromRequestCallback implements MethodInterceptor {
		static final String INTERCEPTOR_METHOD_NAME = "retrieveTicketFromRequest";
		private String paramName ;

		RetrieveTicketFromRequestCallback(AbstractCasFilter f){
			Field protocol;
			try {
				protocol = AbstractCasFilter.class.getDeclaredField("protocol");
				protocol.setAccessible(true);
				Protocol p = (Protocol) protocol.get(f);
				this.paramName = p.getArtifactParameterName();
			} catch (Exception e) {
				logger.error("获取 AbstractCasFilter 类的protocol属性失败，请检查CAS版本以及是否存在安全管理器。");
				throw new UnsupportedOperationException(e);
			}
		}
		
		@Override
		public Object intercept(Object proxyObj, Method method, Object[] args, MethodProxy proxyMethod) throws Throwable {
			HttpServletRequest request = (HttpServletRequest) args[0] ;
			return request.getParameter(paramName);
		}
	}
	
	/**
	 * 传一个基本的AbstractCasFilter类对象，然后将其转换为支持POST传ticket的过滤器
	 */
	private AbstractCasFilter createAllowPostTicketFilter(AbstractCasFilter filter) {
		// cas 的三个协议操作类都是public且可继承的，因此这里可以直接用getClass()
		Enhancer en = new Enhancer();
		en.setSuperclass(filter.getClass());
		en.setCallbacks(new Callback[]{
				NoOp.INSTANCE,
				new RetrieveTicketFromRequestCallback(filter)
		});
		en.setCallbackFilter(m -> RetrieveTicketFromRequestCallback.INTERCEPTOR_METHOD_NAME.equals(m.getName()) ? 1 : 0);
		return (AbstractCasFilter) en.create();
	}
	
	/**
	 * 这个bean的创建代码在{@link CasClientConfiguration}类的100行左右，基本都是无参构造
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void configureValidationFilter(FilterRegistrationBean validationFilter) {
		validationFilter.setOrder(1);
		if (casConfiguration.isAllowPostTicket()) {
			Filter filter = validationFilter.getFilter();
			if (filter instanceof AbstractCasFilter) {
				filter = createAllowPostTicketFilter((AbstractCasFilter)filter);
				validationFilter.setFilter(filter);
				
				if (logger.isInfoEnabled()) {
					logger.info("cas被修改为支持获取post方式提交的ticket参数！");
				}
			} else {
				if (logger.isWarnEnabled()) {
					logger.warn(
							"cas被修改为支持获取post方式提交的ticket参数，但是目前使用的cas版本的ValidationFilter不是AbstractCasFilter的子类，目前是：{}",
							filter == null ? "null" : filter.getClass());
				}
			}
		}
		super.configureValidationFilter(validationFilter);
	}

	@Override
	public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
		authenticationFilter.setOrder(2);
		
		// url过滤
		if (!StringUtils.isEmpty(this.casConfiguration.getIgnorePattern())) {
			authenticationFilter.addInitParameter(ConfigurationKeys.IGNORE_PATTERN.getName(),
					this.casConfiguration.getIgnorePattern());
		}
		super.configureAuthenticationFilter(authenticationFilter);
	}

	/**
	 * 此过滤器在AuthenticationFilter之后，目的是将cas自己的用户认证信息进行替换，但是原始功能保留。
	 */
	@Bean
	public FilterRegistrationBean<CasSessionUserCreateFilter> configureSessionUserFilter(CasSessionUserCreateFilter casSessionUserCreateFilter) {
		FilterRegistrationBean<CasSessionUserCreateFilter> filterBean = new FilterRegistrationBean<>(casSessionUserCreateFilter);
		filterBean.setOrder(3);
		return filterBean;
	}

	/**
	 * 更改顺序+1
	 */
	@Override
	public void configureHttpServletRequestWrapperFilter(FilterRegistrationBean httpServletRequestWrapperFilter) {
		httpServletRequestWrapperFilter.setOrder(4);
		super.configureHttpServletRequestWrapperFilter(httpServletRequestWrapperFilter);
	}

	/**
	 * 更改顺序+1
	 */
	@Override
	public void configureAssertionThreadLocalFilter(FilterRegistrationBean assertionThreadLocalFilter) {
		// XXX 这里只是存储一个threadlocal，看是否用到，没有就去掉
		assertionThreadLocalFilter.setOrder(5);
		super.configureAssertionThreadLocalFilter(assertionThreadLocalFilter);
	}

}
